package com.quick.proxy.server;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.quick.proxy.core.*;
import com.quick.proxy.core.pojo.ProxyMapping;
import com.quick.proxy.core.utils.ByteUtils;
import com.quick.proxy.core.utils.ResponseUtils;
import com.quick.proxy.server.config.ServerConfig;
import com.quick.proxy.server.event.VisitorProxyServerCreateEvent;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.*;


/**
 * Description: 代理消息身份验证处理
 * Author: 吃素不吃肉
 * Date: 2023/7/4
 *
 * @author 吃素不吃肉
 */
@Match(CmdTypeConstants.AUTH)
@Component
@Slf4j
public class ProxyMessageAuthHandler implements ProxyMessageHandler {
    @Autowired
    private ServerConfig serverConfig;

    @Autowired
    private ApplicationContext applicationContext;

    /**
     * 接收的数据帧包
     *
     * @param ctx          上下文
     * @param proxyMessage 帧报文
     */
    @Override
    public void cmdResponse(ChannelHandlerContext ctx, ProxyMessage proxyMessage) {
        JSONObject authRequest = getAuthRequest(proxyMessage);
        for (Map.Entry<String, Object> entry : authRequest.entrySet()) {
            String accessKey = entry.getKey();
            //认证逻辑
            if (auth(ctx, accessKey)) {
                //1.绑定当前链路
                ChannelConstants.ACCESS_KEY_PROXY_CHANNEL.put(accessKey, ctx.channel());
                //2.保存accessKey
                ctx.channel().attr(ChannelAttribute.ACCESS_KEY_PROXY_CHANNEL).set(accessKey);
                //客户端上传的映射信息
                List<ProxyMapping> accessKeyProxyMappingByClient = Optional.ofNullable(JSONUtil.toList(JSONUtil.toJsonStr(entry.getValue()), ProxyMapping.class)).orElse(new ArrayList<>());
                //服务端保存的映射信息
                List<ProxyMapping> accessKeyProxyMappingByServer = serverConfig.getProxyMappings().get(accessKey);
                //过滤映射数据
                List<ProxyMapping> accessKeyProxyMapping = filterProxyMapping(accessKey, accessKeyProxyMappingByClient, accessKeyProxyMappingByServer);
                //3.启动服务
                applicationContext.publishEvent(new VisitorProxyServerCreateEvent(this, ctx, accessKeyProxyMapping, accessKey));
                //4.通知认证结果到代理客户端
                Map<String, Object> data = new HashMap<>();
                data.put(accessKey, accessKeyProxyMapping);
                ResponseUtils.success(ctx.channel(), CmdTypeConstants.AUTH, data);
            }
        }
    }

    /**
     * 过滤映射数据
     *
     * @param accessKey                     访问密钥
     * @param accessKeyProxyMappingByClient 客户端的映射信息
     * @param accessKeyProxyMappingByServer 服务端的映射信息
     * @return 映射数据
     */
    private List<ProxyMapping> filterProxyMapping(String accessKey, List<ProxyMapping> accessKeyProxyMappingByClient, List<ProxyMapping> accessKeyProxyMappingByServer) {
        if (serverConfig.isClientRegisterProxyMappingEnable()
                && CollectionUtils.isEmpty(serverConfig.getClientRegisterProxyMappingAccessKey())) {
            return accessKeyProxyMappingByClient;
        } else if (serverConfig.isClientRegisterProxyMappingEnable()
                && !CollectionUtils.isEmpty(serverConfig.getClientRegisterProxyMappingAccessKey())
                && serverConfig.getClientRegisterProxyMappingAccessKey().contains(accessKey)
        ) {
            return accessKeyProxyMappingByClient;
        }

        return accessKeyProxyMappingByServer;
    }

    /**
     * 获取认证请求内容
     *
     * @param proxyMessage 代理消息
     * @return 认证请求内容
     */
    private JSONObject getAuthRequest(ProxyMessage proxyMessage) {
        String accessKeyData = ByteUtils.hexToString(proxyMessage.getData());
        JSONObject jsonData;
        if (JSONUtil.isTypeJSON(accessKeyData)) {
            jsonData = JSONUtil.parseObj(accessKeyData);
        } else {
            jsonData = new JSONObject();
            jsonData.putOpt(accessKeyData, new ArrayList<>());
        }
        return jsonData;
    }

    /**
     * @param ctx       上下文
     * @param accessKey 访问密钥
     * @return true 认证成功,false认证失败
     */
    private boolean auth(ChannelHandlerContext ctx, String accessKey) {
        Channel channel = ChannelConstants.ACCESS_KEY_PROXY_CHANNEL.get(accessKey);
        if (StrUtil.isEmpty(accessKey)) {
            ResponseUtils.fail(ctx.channel(), CmdTypeConstants.AUTH, StrUtil.format("accessKey [{}] not fund", accessKey));
            return false;
        }
        //如果accessKey已经在使用中,连接失败
        else if (channel != null) {
            ResponseUtils.fail(ctx.channel(), CmdTypeConstants.AUTH, StrUtil.format("accessKey [{}] already used", accessKey));
            return false;
        }
        //如果accessKey不存在,连接失败
        else if (StrUtil.isEmpty(accessKey)
                || (serverConfig.isClientRegisterProxyMappingEnable()
                && !serverConfig.getClientRegisterProxyMappingAccessKey().contains(accessKey))
                && !serverConfig.getProxyMappings().containsKey(accessKey)) {
            ResponseUtils.fail(ctx.channel(), CmdTypeConstants.AUTH, StrUtil.format("accessKey [{}] invalid", accessKey));
            return false;
        }
        return true;
    }
}
